﻿using System;

namespace SilverlightTetris_M10CDE.Web
{
    /// <summary>
    /// Facade controller interacts with game logic
    /// </summary>
    public class TetrisControl
    {
        public Shape currentShape;//the active undocked shape
        public Shape nextShape;//the next shape to become active
        public ShapeFactory myShapeFactory;//used to generate shapes
        public GameField thisGameField;//used to store some of the game state (score, lines removed, docked blocks position)
        public int[] linesToRemove;

        //arrays to hold X and Y coordinates of each block in the active shape
        public int[] locationX;
        public int[] locationY;

        public bool ableToMoveDown;//for gravity moves
        public bool ableToMoveLeft;
        public bool ableToMoveRight;

        private bool stillProcessing;

        public int minY;
        public int maxY;

        private int totalScore;
        private int totalLines;

        /// <summary>
        /// Constructor
        /// </summary>
        public TetrisControl()
        {
            thisGameField = new GameField();
            myShapeFactory = new ShapeFactory();

            //used in SetAbleToMove(), moveLeft() and moveRight()
            locationX = new int[4];
            locationY = new int[4];

            linesToRemove = new int[4];
        }

        /// <summary>
        /// Requests a new shape from shape factory
        /// </summary>
        public void generateShape()
        {
            currentShape = null;
            if (nextShape == null)
            {
                currentShape = myShapeFactory.getShape();
                nextShape = myShapeFactory.getShape();
            }
            else
            {
                currentShape = nextShape;
                nextShape = myShapeFactory.getShape();
            }
            ableToMoveDown = true;
            setAbleToMove();
        }

        /// <summary>
        /// Returns a reference to the currently active shape
        /// </summary>
        /// <returns>a shape reference</returns>
        public Shape getShape()
        {
            return currentShape;
        }

        /// <summary>
        /// Returns a reference to the next shape to be used
        /// </summary>
        /// <returns>a shape reference</returns>
        public Shape getNextShape()
        {
            return nextShape;
        }

        /// <summary>
        /// returns the Y coordinate representing a row in the dockedBlocks array
        /// </summary>
        /// <param name="line"></param>
        /// <returns>line (an int value)</returns>
        public int getLineToRemove(int line)
        {
            return linesToRemove[line];
        }

        /// <summary>
        /// Check if shape can move
        /// </summary>
        public void setAbleToMove()
        {
            int loopCount = 0;
            while (loopCount < 4)
            {
                //assigns the coordinates for the next location (gravity move) of 
                //each block in the active shape to the locationX and locationY arrays.
                if (currentShape != null && currentShape.getY(loopCount) < 17) //17 is the Y coordinate for the lowest possible screen position
                {
                    locationX[loopCount] = (currentShape.getX(loopCount));
                    locationY[loopCount] = (currentShape.getY(loopCount) + 1);
                }

                if (ableToMoveDown)
                {
                    if (currentShape.getY(loopCount) >= 17 || !thisGameField.checkCoordinates(locationX[loopCount], locationY[loopCount]))
                    {                                           //GameField.CheckCoordinates returns false if the location is occupied
                        ableToMoveDown = false;
                    }
                }
                loopCount++;
            }
        }

        /// <summary>
        /// downward movement performed on game tick or if the down key is pressed
        /// </summary>
        /// <returns>boolean value</returns>
        public bool gravityMove()
        {
            if (stillProcessing)
            {
                return true;
            }

            setAbleToMove();

            if (ableToMoveDown)
            {
                if (!stillProcessing)
                {
                    stillProcessing = true;
                    currentShape.gravityMove();  //moves the Block class objects held by currentShape
                    stillProcessing = false;
                }
                return true;
            }
            else
            {
                dockBlocks();
                return false;
            }
        }

        /// <summary>
        /// Decomposes the active shape and passes each block to GameField's dockBlock method
        /// </summary>
        private void dockBlocks()
        {
            Block myBlock;

            //used to store the range of lines (Y coordinates) occupied by the docked shape
            minY = 23; //23 is the max possible so will always be changed
            maxY = 0;  //0 is min possible etc.

            /*Iterates through each block in the current shape
                passing the block to GameField's DockBlock method.*/
            for (int i = 0; i < 4; i++)
            {
                myBlock = currentShape.GetBlock(i);
                thisGameField.dockBlock(myBlock);

                int X = myBlock.xPosition;
                int Y = myBlock.yPosition;

                //updates minY
                if (Y < minY)
                {
                    minY = Y;
                }
                //updates maxY
                if (Y > maxY)
                {
                    maxY = Y;
                }
            }
            checkLines(minY, maxY); //!!! modified code (moved from within loop to fix problem)
        }

        /// <summary>
        /// Checks each line to see if it can be moved
        /// </summary>
        /// <param name="minY"></param>
        /// <param name="maxY"></param>
        private void checkLines(int minY, int maxY)
        {
            for (int i = 0; i < 4; i++)
            {
                linesToRemove[i] = -1;
            }
            int removedLines = 0;
            //checks each line (Y) occupied by the newly docked shape.
            for (int Y = maxY; Y >= minY; Y--)
            {
                bool completeLine = true;

                //checks each X coordinate for a given Y coordinate in
                //the dockedBlocks 2D array. If any element is empty sets completeLine to false.
                for (int X = 0; X < 15; X++)
                {
                    if (thisGameField.checkCoordinates(X, Y)) //lines are only complete if each array element is not null
                    {
                        completeLine = false;
                    }
                }
                //for complete lines, calls the remove line methods for the given line (Y coordinate)
                if (completeLine)
                {
                    thisGameField.removeLine(Y);
                    linesToRemove[removedLines] = Y;
                    removedLines++; //for score
                    Y++; //(ensures the line is checked again as all lines above will drop.)
                    minY++; //reduces the number of lines to be checked
                }
            }
            totalScore = thisGameField.updateScore(removedLines);
            totalLines = thisGameField.updateLines(removedLines);

        }

        /// <summary>
        /// moves each block in the current shape to the left
        /// </summary>
        public void moveLeft()
        {
            if (stillProcessing)
            {
                return;
            }

            stillProcessing = true;
            ableToMoveLeft = true;

            int loopCount = 0;
            while (loopCount < 4)
            {
                if (currentShape.getX(loopCount) >= 1) //must be at least one width position available to the left
                {
                    locationX[loopCount] = (currentShape.getX(loopCount) - 1);
                    locationY[loopCount] = (currentShape.getY(loopCount));
                }

                if (ableToMoveDown)
                {
                    //check if left move is impossible and sets ableToMoveLeft accordingly
                    if (currentShape.getX(loopCount) <= 0 || !thisGameField.checkCoordinates(locationX[loopCount], locationY[loopCount]))
                    {
                        ableToMoveLeft = false;
                    }
                }
                loopCount++;
            }
            if (ableToMoveLeft)
            {
                currentShape.moveLeft();
            }
            ableToMoveDown = true;
            stillProcessing = false;
        }

        /// <summary>
        /// moves each block in the current shape to the right
        /// </summary>
        public void moveRight()
        {
            if (stillProcessing)
            {
                return;
            }

            stillProcessing = true;
            ableToMoveRight = true;

            int loopCount = 0;
            while (loopCount < 4)
            {

                if (currentShape.getX(loopCount) < 14) //must be at least one pictureBox width available to the left
                {
                    locationX[loopCount] = (currentShape.getX(loopCount) + 1);
                    locationY[loopCount] = (currentShape.getY(loopCount));
                }

                if (ableToMoveDown)
                {
                    //check if right move is impossible and sets ableToMoveRight accordingly
                    if (currentShape.getX(loopCount) >= 14 || !thisGameField.checkCoordinates(locationX[loopCount], locationY[loopCount]))
                    {
                        ableToMoveRight = false;
                    }
                }
                loopCount++;
            }
            if (ableToMoveRight)
            {
                currentShape.moveRight();
            }
            ableToMoveDown = true;
            stillProcessing = false;
        }

        /// <summary>
        /// rotate the shape clockwise
        /// </summary>
        public void rotateShape()
        {
            bool ableToRotate = true;
            int X;
            int Y;

            currentShape.RotateShape(); //updates the Block object locations within currentShape

            //used to check if rotation is possible
            for (int i = 0; i < 4; i++)
            {
                X = currentShape.getX(i);
                Y = currentShape.getY(i);
                if (!thisGameField.checkCoordinates(X, Y))
                {
                    ableToRotate = false;
                }
            }
            if (!ableToRotate)
            {
                currentShape.UndoRotateShape();
            }
        }

        /// <summary>
        /// Returns the current game score
        /// </summary>
        /// <returns>totalScore (int)</returns>
        public int getScore()
        {
            return totalScore;
        }

        /// <summary>
        /// Returns the number of cleared lines
        /// </summary>
        /// <returns>totalLines (int)</returns>
        public int getLines()
        {
            return totalLines;
        }

        /// <summary>
        /// Returns bool to check if shape is able to move down
        /// </summary>
        /// <returns></returns>
        public Boolean getAbleToMove()
        {
            return ableToMoveDown;
        }
    }
}